package com.lianchuang.httpclient;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.net.ssl.SSLHandshakeException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.lianchuang.util.BaseResult;
import com.lianchuang.util.StringUtil;

public class HttpClientUtil {

	private static final Logger LOGGER = Logger.getLogger(HttpClientUtil.class);

	private static final String CHARSET_UTF8 = "UTF-8";
	private static final String CHARSET_GBK = "GBK";
	private static final String SSL_DEFAULT_SCHEME = "https";
	private static final int SSL_DEFAULT_PORT = 443;

	// 异常自动恢复处理, 使用HttpRequestRetryHandler接口实现请求的异常恢复
	private static HttpRequestRetryHandler requestRetryHandler = new HttpRequestRetryHandler() {
		// 自定义的恢复策略
		public boolean retryRequest(IOException exception, int executionCount,
				HttpContext context) {
			// 设置恢复策略，在发生异常时候将自动重试3次
			if (executionCount >= 3) {
				return false;
			}
			if (exception instanceof NoHttpResponseException) {
				return true;
			}
			if (exception instanceof SSLHandshakeException) {
				return false;
			}
			HttpRequest request = (HttpRequest) context
					.getAttribute(ExecutionContext.HTTP_REQUEST);
			boolean idempotent = (request instanceof HttpEntityEnclosingRequest);
			if (!idempotent) {
				return true;
			}
			return false;
		}
	};
	// 使用ResponseHandler接口处理响应，HttpClient使用ResponseHandler会自动管理连接的释放，解决了对连接的释放管理
	private static ResponseHandler<String> responseHandler = new ResponseHandler<String>() {
		// 自定义响应处理
		public String handleResponse(HttpResponse response)
				throws ClientProtocolException, IOException {
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				String charset = EntityUtils.getContentCharSet(entity) == null ? CHARSET_GBK
						: EntityUtils.getContentCharSet(entity);
				return new String(EntityUtils.toByteArray(entity), charset);
			} else {
				return null;
			}
		}
	};

	/**
	 * Get方式提交,URL中包含查询参数, 格式：http://www.g.cn?search=p&name=s.....
	 * 
	 * @param url
	 *            提交地址
	 * @return 响应消息
	 */
	public static String get(String url) {
		String result = "";
		try {
			result = get(url, null, CHARSET_UTF8);
		} catch (Exception e) {
			LOGGER.error("Get方式提交,URL中包含查询参数异常：", e);
		}
		return result;
	}
	
	public static String get(String url,String charSet,Map<String,String> header) {
		String result = "";
		try {
			result = get(url, null, charSet,header);
		} catch (Exception e) {
			LOGGER.error("Get方式提交,URL中包含查询参数异常：", e);
		}
		return result;
	}

	/**
	 * Get方式提交,URL中不包含查询参数, 格式：http://www.g.cn
	 * 
	 * @param url
	 *            提交地址
	 * @param params
	 *            查询参数集, 键/值对
	 * @return 响应消息
	 */
	public static String get(String url, Map<String, Object> params) {
		String result = "";
		try {
			result = get(url, params, CHARSET_UTF8);
		} catch (Exception e) {
			LOGGER.error("Get方式提交,URL中包含查询参数异常：", e);
		}
		return result;
	}

	/**
	 * @param url
	 *            提交地址
	 * @param params
	 *            查询参数集, 键/值对
	 * @param charset
	 *            参数提交编码集
	 * @return 响应消息
	 * @throws Exception
	 */
	public static String get(String url, Map<String, Object> params,
			String charset,Map<String,String> headers) throws Exception {
		if (url == null || StringUtil.isEmpty(url)) {
			return null;
		}
		List<NameValuePair> qparams = getParamsList(params);
		if (qparams != null && qparams.size() > 0) {
			charset = (charset == null ? CHARSET_GBK : charset);
			String formatParams = URLEncodedUtils.format(qparams, charset);
			url = (url.indexOf("?")) < 0 ? (url + "?" + formatParams) : (url
					.substring(0, url.indexOf("?") + 1) + formatParams);
		}
		DefaultHttpClient httpclient = getDefaultHttpClient(charset);
		HttpGet hg = new HttpGet(url);
		if(headers != null){
			for(Entry<String,String> m : headers.entrySet()){
				hg.addHeader(m.getKey(), m.getValue());
			}
		}
		// 发送请求，得到响应
		String responseStr = null;
		try {
			responseStr = httpclient.execute(hg, responseHandler);
		} catch (ClientProtocolException e) {
			throw new Exception("客户端连接协议错误", e);
		} catch (IOException e) {
			throw new Exception("IO操作异常", e);
		} finally {
			abortConnection(hg, httpclient);
		}
		return responseStr;
	}
	
	public static String get(String url, Map<String, Object> params,
			String charset) throws Exception {
		if (url == null || StringUtil.isEmpty(url)) {
			return null;
		}
		List<NameValuePair> qparams = getParamsList(params);
		if (qparams != null && qparams.size() > 0) {
			charset = (charset == null ? CHARSET_GBK : charset);
			String formatParams = URLEncodedUtils.format(qparams, charset);
			url = (url.indexOf("?")) < 0 ? (url + "?" + formatParams) : (url
					.substring(0, url.indexOf("?") + 1) + formatParams);
		}
		DefaultHttpClient httpclient = getDefaultHttpClient(charset);
		HttpGet hg = new HttpGet(url);
		
		// 发送请求，得到响应
		String responseStr = null;
		try {
			responseStr = httpclient.execute(hg, responseHandler);
		} catch (ClientProtocolException e) {
			throw new Exception("客户端连接协议错误", e);
		} catch (IOException e) {
			throw new Exception("IO操作异常", e);
		} finally {
			abortConnection(hg, httpclient);
		}
		return responseStr;
	}

	/**
	 * Post方式提交,URL中不包含提交参数, 格式：http://www.g.cn
	 * 
	 * @param url
	 *            提交地址
	 * @param params
	 *            提交参数集, 键/值对
	 * @return 响应消息
	 * @throws Exception
	 */
	public static String post(String url, Map<String, Object> params)
			throws Exception {
		return post(url, params, CHARSET_UTF8);
	}

	public static String post(String url, HttpEntity entity) throws Exception {
		return post(url, entity, CHARSET_UTF8);
	}

	/**
	 * Post方式提交,URL中不包含提交参数, 格式：http://www.g.cn
	 * 
	 * @param url
	 *            提交地址
	 * @param params
	 *            提交参数集, 键/值对
	 * @param charset
	 *            参数提交编码集
	 * @return 响应消息
	 * @throws Exception
	 */
	public static String post(String url, HttpEntity entity, String charset)
			throws Exception {
		if (url == null || StringUtil.isEmpty(url)) {
			return null;
		}
		// 创建HttpClient实例
		DefaultHttpClient httpclient = getDefaultHttpClient(charset);
		HttpPost hp = new HttpPost(url);
		hp.setEntity(entity);
		// 发送请求，得到响应
		String responseStr = null;
		try {
			responseStr = httpclient.execute(hp, responseHandler);
		} catch (ClientProtocolException e) {
			throw new Exception("客户端连接协议错误", e);
		} catch (IOException e) {
			throw new Exception("IO操作异常", e);
		} finally {
			abortConnection(hp, httpclient);
		}
		return responseStr;
	}
	 /**
     * 添加头
     * @param httpRequest
     * @param headerMap
     */
    private static void addHeaders(HttpEntityEnclosingRequestBase httpRequest, Map<String, String> headerMap){
        for(Map.Entry<String, String> entry : headerMap.entrySet()){
            httpRequest.addHeader(entry.getKey(), entry.getValue());
        }
    }
    public static String post(String url, Map<String, Object> params,
			String charset,Map<String,String> headers) throws Exception {
		if (url == null || StringUtil.isEmpty(url)) {
			return null;
		}

		// 创建HttpClient实例
		DefaultHttpClient httpclient = getDefaultHttpClient(charset);
		UrlEncodedFormEntity formEntity = null;
		try {
			if (charset == null || StringUtil.isEmpty(charset)) {
				formEntity = new UrlEncodedFormEntity(getParamsList(params));
			} else {
				formEntity = new UrlEncodedFormEntity(getParamsList(params),
						charset);
			}
		} catch (UnsupportedEncodingException e) {
			throw new Exception("不支持的编码集", e);
		}
		HttpPost hp = new HttpPost(url);
		hp.setEntity(formEntity);
		if(headers != null){
			addHeaders(hp, headers);
		}
		// 发送请求，得到响应
		String responseStr = null;
		try {
			responseStr = httpclient.execute(hp, responseHandler);
		} catch (ClientProtocolException e) {
			throw new Exception("客户端连接协议错误", e);
		} catch (IOException e) {
			throw new Exception("IO操作异常", e);
		} finally {
			abortConnection(hp, httpclient);
		}
		return responseStr;
	}
	public static String post(String url, Map<String, Object> params,
			String charset) throws Exception {
		if (url == null || StringUtil.isEmpty(url)) {
			return null;
		}
		// 创建HttpClient实例
		DefaultHttpClient httpclient = getDefaultHttpClient(charset);
		UrlEncodedFormEntity formEntity = null;
		try {
			if (charset == null || StringUtil.isEmpty(charset)) {
				formEntity = new UrlEncodedFormEntity(getParamsList(params));
			} else {
				formEntity = new UrlEncodedFormEntity(getParamsList(params),
						charset);
			}
		} catch (UnsupportedEncodingException e) {
			throw new Exception("不支持的编码集", e);
		}
		HttpPost hp = new HttpPost(url);
		hp.setEntity(formEntity);
		// 发送请求，得到响应
		String responseStr = null;
		try {
			responseStr = httpclient.execute(hp, responseHandler);
		} catch (ClientProtocolException e) {
			throw new Exception("客户端连接协议错误", e);
		} catch (IOException e) {
			throw new Exception("IO操作异常", e);
		} finally {
			abortConnection(hp, httpclient);
		}
		return responseStr;
	}

	/**
	 * Post方式提交,忽略URL中包含的参数,解决SSL双向数字证书认证
	 *
	 * @param url
	 *            提交地址
	 * @param params
	 *            提交参数集, 键/值对
	 * @param charset
	 *            参数编码集
	 * @param keystoreUrl
	 *            密钥存储库路径
	 * @param keystorePassword
	 *            密钥存储库访问密码
	 * @param truststoreUrl
	 *            信任存储库绝路径
	 * @param truststorePassword
	 *            信任存储库访问密码, 可为null
	 * @return 响应消息
	 * @throws Exception
	 */
	public static String post(String url, Map<String, Object> params,
			String charset, final URL keystoreUrl,
			final String keystorePassword, final URL truststoreUrl,
			final String truststorePassword) throws Exception {
		if (url == null || StringUtil.isEmpty(url)) {
			return null;
		}
		DefaultHttpClient httpclient = getDefaultHttpClient(charset);
		UrlEncodedFormEntity formEntity = null;
		try {
			if (charset == null || StringUtil.isEmpty(charset)) {
				formEntity = new UrlEncodedFormEntity(getParamsList(params));
			} else {
				formEntity = new UrlEncodedFormEntity(getParamsList(params),
						charset);
			}
		} catch (UnsupportedEncodingException e) {
			throw new Exception("不支持的编码集", e);
		}
		HttpPost hp = null;
		String responseStr = null;
		try {
			KeyStore keyStore = createKeyStore(keystoreUrl, keystorePassword);
			KeyStore trustStore = createKeyStore(truststoreUrl,
					keystorePassword);
			SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore,
					keystorePassword, trustStore);
			Scheme scheme = new Scheme(SSL_DEFAULT_SCHEME, socketFactory,
					SSL_DEFAULT_PORT);
			httpclient.getConnectionManager().getSchemeRegistry()
					.register(scheme);
			hp = new HttpPost(url);
			hp.setEntity(formEntity);
			responseStr = httpclient.execute(hp, responseHandler);
		} catch (NoSuchAlgorithmException e) {
			throw new Exception("指定的加密算法不可用", e);
		} catch (KeyStoreException e) {
			throw new Exception("keytore解析异常", e);
		} catch (CertificateException e) {
			throw new Exception("信任证书过期或解析异常", e);
		} catch (FileNotFoundException e) {
			throw new Exception("keystore文件不存在", e);
		} catch (IOException e) {
			throw new Exception("I/O操作失败或中断 ", e);
		} catch (UnrecoverableKeyException e) {
			throw new Exception("keystore中的密钥无法恢复异常", e);
		} catch (KeyManagementException e) {
			throw new Exception("处理密钥管理的操作异常", e);
		} finally {
			abortConnection(hp, httpclient);
		}
		return responseStr;
	}

	/**
	 * 获取DefaultHttpClient实例
	 *
	 * @param charset
	 *            参数编码集, 可空
	 * @return DefaultHttpClient 对象
	 */
	private static DefaultHttpClient getDefaultHttpClient(final String charset) {
		DefaultHttpClient httpclient = new DefaultHttpClient();
		httpclient.getParams().setParameter(
				CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);
		// 模拟浏览器，解决一些服务器程序只允许浏览器访问的问题
		httpclient.getParams().setParameter(CoreProtocolPNames.USER_AGENT,
				"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)");
		httpclient.getParams().setParameter(
				CoreProtocolPNames.USE_EXPECT_CONTINUE, Boolean.FALSE);
		httpclient.getParams().setParameter(
				CoreProtocolPNames.HTTP_CONTENT_CHARSET,
				charset == null ? CHARSET_GBK : charset);
		httpclient.setHttpRequestRetryHandler(requestRetryHandler);
		return httpclient;
	}

	/**
	 * 释放HttpClient连接
	 *
	 * @param hrb
	 *            请求对象
	 * @param httpclient
	 *            client对象
	 */
	private static void abortConnection(final HttpRequestBase hrb,
			final HttpClient httpclient) {
		if (hrb != null) {
			hrb.abort();
		}
		if (httpclient != null) {
			httpclient.getConnectionManager().shutdown();
		}
	}

	/**
	 * 从给定的路径中加载此 KeyStore
	 *
	 * @param url
	 *            keystore URL路径
	 * @param password
	 *            keystore访问密钥
	 * @return keystore 对象
	 */
	private static KeyStore createKeyStore(final URL url, final String password)
			throws KeyStoreException, NoSuchAlgorithmException,
			CertificateException, IOException {
		if (url == null) {
			throw new IllegalArgumentException("Keystore url may not be null");
		}
		KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
		InputStream is = null;
		try {
			is = url.openStream();
			keystore.load(is, password != null ? password.toCharArray() : null);
		} finally {
			if (is != null) {
				is.close();
				is = null;
			}
		}
		return keystore;
	}

	/**
	 * 将传入的键/值对参数转换为NameValuePair参数集
	 *
	 * @param paramsMap
	 *            参数集, 键/值对
	 * @return NameValuePair参数集
	 */
	private static List<NameValuePair> getParamsList(
			Map<String, Object> paramsMap) {
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		if (paramsMap == null || paramsMap.size() == 0) {
			return params;
		}
		for (Map.Entry<String, Object> map : paramsMap.entrySet()) {
			params.add(new BasicNameValuePair(map.getKey(), map.getValue()
					.toString()));
		}
		return params;
	}

	/**
	 * <p>
	 * Description:http post 远程请求，注意：适用于返回json串的远程请求
	 * </p>
	 * 
	 * @date 2016-4-4 上午9:56:34
	 * @author hwm
	 * @param url
	 *            http远程请求路径
	 * @param entity
	 *            http封装的请求实体
	 * @return 返回结果是json串
	 * @throws Exception
	 */
	public static BaseResult doPost(String url, MultipartEntityBuilder entity)
			throws Exception {
		RequestConfig config = RequestConfig.custom()
				.setCookieSpec(CookieSpecs.STANDARD_STRICT).build();
		CloseableHttpClient httpClient = HttpClients.custom()
				.setDefaultRequestConfig(config).build();
		HttpPost post = new HttpPost(url);
		// 利用实体构造器获取HttpEntity，并设置在post请求实体中
		if (null != entity) {
			post.setEntity(entity.build());
		} else {
			post.setEntity(MultipartEntityBuilder.create().build());
		}
		CloseableHttpResponse response = null;
		BaseResult baseResult = null;
		try {
			response = httpClient.execute(post);
			StatusLine statusLine = response.getStatusLine();
			if (statusLine.getStatusCode() == 200) {
				// 注意EntityUtils.toString(response.getEntity())不能重复使用，只能使用一次
				String result = EntityUtils.toString(response.getEntity());
				baseResult = new ObjectMapper().readValue(result,
						BaseResult.class);
			} else {
				String result = EntityUtils.toString(response.getEntity());
				if (null != result && !result.equals("")) {
					baseResult = new ObjectMapper().readValue(result,
							BaseResult.class);
				} else {
					baseResult = new BaseResult(
							statusLine.getStatusCode() + "",
							statusLine.getReasonPhrase());
				}

			}
		} finally {
			// response用完要关闭
			if (null != response) {
				response.close();
			}
		}
		return baseResult;
	}

	/**
	 * <p>
	 * Description:http post 请求，将响应处理交给上层处理
	 * </p>
	 * 
	 * @date 2016-4-4 上午9:56:34
	 * @author hwm
	 * @param url
	 *            http远程请求路径
	 * @param entity
	 *            http封装的请求实体
	 * @param cookie
	 *            cookie缓存机制，好像是用来缓存用户登陆信息的 (可选)
	 * @param refer
	 *            跳转地址(可选，请求返回成功后要重定向的页面)
	 * @return
	 * @throws Exception
	 */
	public static void doPost(String url, ClientCallBack hcb,
			MultipartEntityBuilder entity, String cookie, String refer)
			throws Exception {
		RequestConfig config = RequestConfig.custom()
				.setCookieSpec(CookieSpecs.STANDARD_STRICT).build();
		CloseableHttpClient httpClient = HttpClients.custom()
				.setDefaultRequestConfig(config).build();
		HttpPost post = new HttpPost(url);
		if (cookie != null) {
			post.setHeader("Cookie", cookie);
		}
		if (refer != null) {
			post.setHeader("Referer", refer);
		}
		// 利用实体构造器获取HttpEntity，并设置在post请求实体中
		if (null != entity) {
			post.setEntity(entity.build());
		} else {
			post.setEntity(MultipartEntityBuilder.create().build());
		}
		CloseableHttpResponse cresponse = null;
		try {
			cresponse = httpClient.execute(post);
			hcb.doResponse(cresponse);
		} finally {
			// response用完要关闭
			if (null != cresponse) {
				cresponse.close();
			}
		}
	}

	/**
	 * ssl请求
	 * 
	 * @date 2016-12-19 上午9:56:34
	 * @author 叶开
	 * @param url
	 * @param params
	 * @param logDesc
	 * @return
	 */
	public static String doPostForSSLClient(String url,
			List<NameValuePair> params, String logDesc) {
		String charset = "UTF-8";
		HttpClient httpClient = null;
		HttpPost httpPost = null;
		String result = null;
		try {
			httpClient = new SSLClient();
			httpPost = new HttpPost(url);
			// 设置参数
			if (null != params && params.size() > 0) {
				UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params,
						charset);
				httpPost.setEntity(entity);
			}
			HttpResponse response = httpClient.execute(httpPost);
			if (response != null) {
				HttpEntity resEntity = response.getEntity();
				if (resEntity != null) {
					result = EntityUtils.toString(resEntity, charset);
				}
			}
		} catch (Exception ex) {
			LOGGER.error(logDesc + "Http post请求方法异常：", ex);
		}
		return result;
	}
	
	
	public static void main(String[] args) {
		String url = "http://cont.jsdyms.com/bdata?race_date=23-04-2017&race_type=6M&rc=1&m=HK&c=3&lu=0";
		String result = HttpClientUtil.get(url);
		System.out.println(result);
	}
}
